package com.carey.blog.jsonrpc.impl;

import static com.carey.blog.model.Preference.ADMIN_EMAIL;
import static com.carey.blog.model.Preference.ARTICLE_LIST_DISPLAY_COUNT;
import static com.carey.blog.model.Preference.ARTICLE_LIST_PAGINATION_WINDOW_SIZE;
import static com.carey.blog.model.Preference.BLOG_HOST;
import static com.carey.blog.model.Preference.BLOG_SUBTITLE;
import static com.carey.blog.model.Preference.BLOG_TITLE;
import static com.carey.blog.model.Preference.ENABLE_ARTICLE_UPDATE_HINT;
import static com.carey.blog.model.Preference.ENABLE_POST_TO_BUZZ;
import static com.carey.blog.model.Preference.GOOGLE_OAUTH_CONSUMER_SECRET;
import static com.carey.blog.model.Preference.HTML_HEAD;
import static com.carey.blog.model.Preference.LOCALE_STRING;
import static com.carey.blog.model.Preference.META_DESCRIPTION;
import static com.carey.blog.model.Preference.META_KEYWORDS;
import static com.carey.blog.model.Preference.MOST_COMMENT_ARTICLE_DISPLAY_CNT;
import static com.carey.blog.model.Preference.MOST_USED_TAG_DISPLAY_CNT;
import static com.carey.blog.model.Preference.NOTICE_BOARD;
import static com.carey.blog.model.Preference.PREFERENCE;
import static com.carey.blog.model.Preference.RECENT_ARTICLE_DISPLAY_CNT;
import static com.carey.blog.model.Preference.RECENT_COMMENT_DISPLAY_CNT;
import static com.carey.blog.model.Preference.SIGNS;
import static com.carey.blog.model.Preference.TIME_ZONE_ID;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.carey.blog.action.StatusCodes;
import com.carey.blog.action.captcha.CaptchaServlet;
import com.carey.blog.action.impl.AddArticleCommentAction;
import com.carey.blog.event.EventTypes;
import com.carey.blog.framework.latke.Keys;
import com.carey.blog.framework.latke.action.ActionException;
import com.carey.blog.framework.latke.action.util.PageCaches;
import com.carey.blog.framework.latke.action.util.Paginator;
import com.carey.blog.framework.latke.event.Event;
import com.carey.blog.framework.latke.event.EventManager;
import com.carey.blog.framework.latke.model.Pagination;
import com.carey.blog.framework.latke.model.Role;
import com.carey.blog.framework.latke.model.User;
import com.carey.blog.framework.latke.repository.Query;
import com.carey.blog.framework.latke.repository.RepositoryException;
import com.carey.blog.framework.latke.repository.Transaction;
import com.carey.blog.framework.latke.util.freemarker.Templates;
import com.carey.blog.jsonrpc.AbstractGAEJSONRpcService;
import com.carey.blog.listener.CareyBlogServletListener;
import com.carey.blog.model.Article;
import com.carey.blog.model.Cache;
import com.carey.blog.model.Comment;
import com.carey.blog.model.Preference;
import com.carey.blog.model.Skin;
import com.carey.blog.model.Statistic;
import com.carey.blog.repository.PreferenceRepository;
import com.carey.blog.repository.StatisticRepository;
import com.carey.blog.repository.UserRepository;
import com.carey.blog.repository.impl.PreferenceGAERepository;
import com.carey.blog.repository.impl.StatisticGAERepository;
import com.carey.blog.repository.impl.UserGAERepository;
import com.carey.blog.util.Preferences;
import com.carey.blog.util.Skins;
import com.carey.blog.util.TimeZones;
import com.carey.blog.util.Users;
import com.google.appengine.api.users.UserServiceFactory;

/**
 * Administrator service for JavaScript client.
 * 
 */
public final class AdminService extends AbstractGAEJSONRpcService {

	/**
	 * Logger.
	 */
	private static final Logger LOGGER = Logger.getLogger(AdminService.class
			.getName());
	/**
	 * User service.
	 */
	private com.google.appengine.api.users.UserService userService = UserServiceFactory
			.getUserService();
	/**
	 * Preference repository.
	 */
	private PreferenceRepository preferenceRepository = PreferenceGAERepository
			.getInstance();
	/**
	 * User repository.
	 */
	private UserRepository userRepository = UserGAERepository.getInstance();
	/**
	 * Statistic repository.
	 */
	private StatisticRepository statisticRepository = StatisticGAERepository
			.getInstance();
	/**
	 * Event manager.
	 */
	private EventManager eventManager = EventManager.getInstance();
	/**
	 * Skin utilities.
	 */
	private Skins skins = Skins.getInstance();
	/**
	 * User utilities.
	 */
	private Users userUtils = Users.getInstance();
	/**
	 * Preference utilities.
	 */
	private Preferences preferenceUtils = Preferences.getInstance();
	/**
	 * Article service.
	 */
	private ArticleService articleService = ArticleService.getInstance();
	/**
	 * Time zone utilities.
	 */
	private TimeZones timeZoneUtils = TimeZones.getInstance();

	/**
	 * Removes a user with the specified request json object.
	 * 
	 * @param requestJSONObject
	 *            the specified request json object, for example,
	 * 
	 * <pre>
	 * {
	 *     "oId": ""
	 * }
	 * </pre>
	 * @param request
	 *            the specified http servlet request
	 * @param response
	 *            the specified http servlet response
	 * @return for example,
	 * 
	 * <pre>
	 * {
	 *     "sc": "REMOVE_USER_SUCC"
	 * }
	 * </pre>
	 * @throws ActionException
	 *             action exception
	 * @throws IOException
	 *             io exception
	 */
	public JSONObject removeUser(final JSONObject requestJSONObject,
			final HttpServletRequest request, final HttpServletResponse response)
			throws ActionException, IOException {
		final JSONObject ret = new JSONObject();
		if (!userUtils.isAdminLoggedIn()) {
			response.sendError(HttpServletResponse.SC_FORBIDDEN);
			return ret;
		}

		final Transaction transaction = userRepository.beginTransaction();
		try {
			final String userId = requestJSONObject.getString(Keys.OBJECT_ID);

			final int userLength = userRepository.get(new Query())
					.getJSONArray(Keys.RESULTS).length();
			if (2 == userLength) {
				if ("valentine".equals(preferenceUtils.getPreference()
						.getString(Skin.SKIN_DIR_NAME))) {
					if (transaction.isActive()) {
						transaction.rollback();
					}

					ret.put(Keys.STATUS_CODE,
							StatusCodes.REMOVE_USER_FAIL_SKIN_NEED_MUL_USERS);
					return ret;
				}
			}

			userRepository.remove(userId);
			transaction.commit();

			ret.put(Keys.STATUS_CODE, StatusCodes.REMOVE_USER_SUCC);
		} catch (final Exception e) {
			if (transaction.isActive()) {
				transaction.rollback();
			}
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new ActionException(e);
		}

		PageCaches.removeAll();

		return ret;
	}

	/**
	 * Adds a user(default role) with the specified request json object.
	 * 
	 * @param requestJSONObject
	 *            the specified request json object, for example,
	 * 
	 * <pre>
	 * {
	 *     "userName": "",
	 *     "userEmail": "",
	 *     "userRole": "" // optional, uses {@value Role#DEFAULT_ROLE} instead,
	 *                       if not speciffied
	 * }
	 * </pre>
	 * @param request
	 *            the specified http servlet request
	 * @param response
	 *            the specified http servlet response
	 * @return for example,
	 * 
	 *         <pre>
	 * {
	 *     "sc": "ADD_USER_SUCC"
	 * }
	 * </pre>
	 * @throws ActionException
	 *             action exception
	 * @throws IOException
	 *             io exception
	 */
	public JSONObject addUser(final JSONObject requestJSONObject,
			final HttpServletRequest request, final HttpServletResponse response)
			throws ActionException, IOException {
		final JSONObject ret = new JSONObject();
		if (!userUtils.isAdminLoggedIn()) {
			response.sendError(HttpServletResponse.SC_FORBIDDEN);
			return ret;
		}

		final Transaction transaction = userRepository.beginTransaction();
		try {
			final JSONObject user = new JSONObject();
			final String userEmail = requestJSONObject
					.getString(User.USER_EMAIL).trim().toLowerCase();
			final JSONObject duplicatedUser = userRepository
					.getByEmail(userEmail);
			if (null != duplicatedUser) {
				ret.put(Keys.STATUS_CODE,
						StatusCodes.ADD_USER_FAIL_DUPLICATED_EMAIL);

				return ret;
			}

			final String userName = requestJSONObject.getString(User.USER_NAME);
			user.put(User.USER_EMAIL, userEmail);
			user.put(User.USER_NAME, userName);
			final String roleName = requestJSONObject.optString(User.USER_ROLE,
					Role.DEFAULT_ROLE);
			user.put(User.USER_ROLE, roleName);
			userRepository.add(user);
			transaction.commit();

			ret.put(Keys.STATUS_CODE, StatusCodes.ADD_USER_SUCC);
		} catch (final Exception e) {
			if (transaction.isActive()) {
				transaction.rollback();
			}
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new ActionException(e);
		}

		PageCaches.removeAll();

		return ret;
	}

	/**
	 * Gets a user by the specified request json object.
	 * 
	 * @param requestJSONObject
	 *            the specified request json object, for example,
	 * 
	 *            <pre>
	 * {
	 *     "oId": ""
	 * }
	 * </pre>
	 * @return for example,
	 * 
	 *         <pre>
	 * {
	 *     "user": {
	 *         "oId": "",
	 *         "userName": "",
	 *         "userEmail": ""
	 *     },
	 *     "sc": "GET_USER_SUCC"
	 * }
	 * </pre>
	 * @throws ActionException
	 *             action exception
	 */
	public JSONObject getUser(final JSONObject requestJSONObject)
			throws ActionException {
		final JSONObject ret = new JSONObject();

		try {
			final String userId = requestJSONObject.getString(Keys.OBJECT_ID);
			final JSONObject user = userRepository.get(userId);
			ret.put(User.USER, user);

			ret.put(Keys.STATUS_CODE, StatusCodes.GET_USER_SUCC);

			LOGGER.log(Level.FINER, "Got a user[oId={0}]", userId);
		} catch (final Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new ActionException(e);
		}

		return ret;
	}

	/**
	 * Gets users by the specified request json object.
	 * 
	 * @param requestJSONObject
	 *            the specified request json object, for example,
	 * 
	 *            <pre>
	 * {
	 *     "paginationCurrentPageNum": 1,
	 *     "paginationPageSize": 20,
	 *     "paginationWindowSize": 10,
	 * }, see {@link Pagination} for more details
	 * </pre>
	 * @param request
	 *            the specified http servlet request
	 * @param response
	 *            the specified http servlet response
	 * @return for example,
	 * 
	 *         <pre>
	 * {
	 *     "pagination": {
	 *         "paginationPageCount": 100,
	 *         "paginationPageNums": [1, 2, 3, 4, 5]
	 *     },
	 *     "users": [{
	 *         "oId": "",
	 *         "userName": "",
	 *         "userEmail": "",
	 *         "roleName": ""
	 *      }, ....]
	 *     "sc": "GET_USERS_SUCC"
	 * }
	 * </pre>
	 * @throws ActionException
	 *             action exception
	 * @throws IOException
	 *             io exception
	 * @see Pagination
	 */
	public JSONObject getUsers(final JSONObject requestJSONObject,
			final HttpServletRequest request, final HttpServletResponse response)
			throws ActionException, IOException {
		final JSONObject ret = new JSONObject();
		if (!userUtils.isAdminLoggedIn()) {
			response.sendError(HttpServletResponse.SC_FORBIDDEN);
			return ret;
		}

		try {
			final int currentPageNum = requestJSONObject
					.getInt(Pagination.PAGINATION_CURRENT_PAGE_NUM);
			final int pageSize = requestJSONObject
					.getInt(Pagination.PAGINATION_PAGE_SIZE);
			final int windowSize = requestJSONObject
					.getInt(Pagination.PAGINATION_WINDOW_SIZE);
			final Query query = new Query().setCurrentPageNum(currentPageNum)
					.setPageSize(pageSize);
			final JSONObject result = userRepository.get(query);

			final int pageCount = result.getJSONObject(Pagination.PAGINATION)
					.getInt(Pagination.PAGINATION_PAGE_COUNT);

			final JSONObject pagination = new JSONObject();
			ret.put(Pagination.PAGINATION, pagination);
			final List<Integer> pageNums = Paginator.paginate(currentPageNum,
					pageSize, pageCount, windowSize);
			pagination.put(Pagination.PAGINATION_PAGE_COUNT, pageCount);
			pagination.put(Pagination.PAGINATION_PAGE_NUMS, pageNums);

			final JSONArray users = result.getJSONArray(Keys.RESULTS);
			ret.put(User.USERS, users);

			ret.put(Keys.STATUS_CODE, StatusCodes.GET_USERS_SUCC);
		} catch (final Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new ActionException(e);
		}

		return ret;
	}

	/**
	 * Updates a user with the specified request json object.
	 * 
	 * @param requestJSONObject
	 *            the specified request json object, for example,
	 * 
	 *            <pre>
	 * {
	 *     "oId": "",
	 *     "userName": "",
	 *     "userEmail": "",
	 *     "userRole": ""
	 * }
	 * </pre>
	 * @param request
	 *            the specified http servlet request
	 * @param response
	 *            the specified http servlet response
	 * @return for example,
	 * 
	 *         <pre>
	 * {
	 *     "sc": "UPDATE_USER_SUCC"
	 * }
	 * </pre>
	 * @throws ActionException
	 *             action exception
	 * @throws IOException
	 *             io exception
	 */
	public JSONObject updateUser(final JSONObject requestJSONObject,
			final HttpServletRequest request, final HttpServletResponse response)
			throws ActionException, IOException {
		final JSONObject ret = new JSONObject();
		if (!userUtils.isAdminLoggedIn()) {
			response.sendError(HttpServletResponse.SC_FORBIDDEN);
			return ret;
		}

		final Transaction transaction = userRepository.beginTransaction();
		try {
			final String oldUserId = requestJSONObject
					.getString(Keys.OBJECT_ID);
			final JSONObject oldUser = userRepository.get(oldUserId);
			final String userNewEmail = requestJSONObject.getString(
					User.USER_EMAIL).trim();

			final String userRole = requestJSONObject.getString(User.USER_ROLE);
			if (Role.ADMIN_ROLE.equals(userRole)) {
				final String adminOldEmail = oldUser.getString(User.USER_EMAIL);
				if (!adminOldEmail.equals(userNewEmail)) {
					// Can't update the admin's email
					ret.put(Keys.STATUS_CODE, StatusCodes.UPDATE_USER_FAIL_);

					return ret;
				}
			}

			// Remove old user
			if (null == oldUser) {
				ret.put(Keys.STATUS_CODE, StatusCodes.UPDATE_USER_FAIL_);

				return ret;
			}

			// Check email is whether duplicated
			final JSONObject mayBeAnother = userRepository
					.getByEmail(userNewEmail);
			if (null != mayBeAnother
					&& !mayBeAnother.getString(Keys.OBJECT_ID)
							.equals(oldUserId)) {
				// Exists someone else has the save email as requested
				ret.put(Keys.STATUS_CODE,
						StatusCodes.ADD_USER_FAIL_DUPLICATED_EMAIL);

				return ret;
			}

			// Update
			final String userName = requestJSONObject.getString(User.USER_NAME);
			oldUser.put(User.USER_EMAIL, userNewEmail);
			oldUser.put(User.USER_NAME, userName);
			// Unchanges the default role

			userRepository.update(oldUserId, oldUser);
			transaction.commit();

			ret.put(Keys.STATUS_CODE, StatusCodes.UPDATE_USER_SUCC);
		} catch (final Exception e) {
			if (transaction.isActive()) {
				transaction.rollback();
			}
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new ActionException(e);
		}

		PageCaches.removeAll();

		return ret;
	}

	/**
	 * Gets page cache states with the specified http servlet request and http
	 * servlet response.
	 * 
	 * @param request
	 *            the specified http servlet request
	 * @param response
	 *            the specified http servlet response
	 * @return for example,
	 * 
	 *         <pre>
	 * {
	 *     "cacheCachedCount": long,
	 *     "cacheHitCount": long,
	 *     "cachedBytes": long,
	 *     "hitBytes": long,
	 *     "cacheMissCount": long
	 * }
	 * </pre>
	 * @throws ActionException
	 *             action exception
	 * @throws IOException
	 *             io exception
	 */
	public JSONObject getPageCache(final HttpServletRequest request,
			final HttpServletResponse response) throws ActionException,
			IOException {
		final JSONObject ret = new JSONObject();
		if (!userUtils.isAdminLoggedIn()) {
			response.sendError(HttpServletResponse.SC_FORBIDDEN);
			return ret;
		}

		final com.carey.blog.framework.latke.cache.Cache<String, Object> cache = PageCaches
				.getCache();
		final long cachedCount = cache.getCachedCount();
		final long hitCount = cache.getHitCount();
		final long missCount = cache.getMissCount();
		final long cachedBytes = cache.getCachedBytes();
		final long hitBytes = cache.getHitBytes();

		try {
			ret.put(Cache.CACHE_CACHED_COUNT, cachedCount);
			ret.put(Cache.CACHE_HIT_COUNT, hitCount);
			ret.put(Cache.CACHE_CACHED_BYTES, cachedBytes);
			ret.put(Cache.CACHE_HIT_BYTES, hitBytes);
			ret.put(Cache.CACHE_MISS_COUNT, missCount);
		} catch (final JSONException e) {
			LOGGER.log(Level.SEVERE, "Get page cache error: {0}",
					e.getMessage());
			throw new ActionException(e);
		}

		return ret;
	}

	/**
	 * Initializes Carey Blog.
	 * 
	 * @param request
	 *            the specified http servlet request
	 * @param response
	 *            the specified http servlet response
	 * @throws ActionException
	 *             action exception
	 * @throws IOException
	 *             io exception
	 * @return for example,
	 * 
	 *         <pre>
	 * {
	 *     "sc": "INIT_CAREY_BLOG_SUCC"
	 * }
	 * </pre>
	 */
	public JSONObject init(final HttpServletRequest request,
			final HttpServletResponse response) throws ActionException,
			IOException {
		final JSONObject ret = new JSONObject();
		if (!userUtils.isAdminLoggedIn()) {
			response.sendError(HttpServletResponse.SC_FORBIDDEN);
			return ret;
		}

		try {
			final JSONObject statistic = statisticRepository
					.get(Statistic.STATISTIC);
			if (null == statistic) {
				initStatistic();
				initPreference();
				initAdmin(request, response);

				helloWorld(request, response);
			}

			ret.put(Keys.STATUS_CODE, StatusCodes.INIT_CAREY_BLOG_SUCC);
		} catch (final Exception e) {
			LOGGER.severe("Initialize Carey Blog error");
		}

		return ret;
	}

	/**
	 * Publishes the first article "Hello World".
	 * 
	 * <p>
	 * <b>Note</b>: The article "Hello World" and its comment is no i18N, all
	 * contents are English.
	 * </p>
	 * 
	 * @param request
	 *            the specified http servlet request
	 * @param response
	 *            the specified http servlet response
	 * @throws RepositoryException
	 *             repository exception
	 * @throws JSONException
	 *             json exception
	 */
	private void helloWorld(final HttpServletRequest request,
			final HttpServletResponse response) throws RepositoryException,
			JSONException {
		LOGGER.info("Hello World!");

		try {
			final JSONObject article = new JSONObject();

			article.put(Article.ARTICLE_TITLE, "Hello World!");
			final String content = "Welcome to <a style=\"text-decoration: none;\" target=\"_blank\" "
					+ "href=\"http://carey-blog.googlecode.com\">"
					+ "<span style=\"color: orange;\">C</span>"
					+ "<span style=\"color: blue;\">"
					+ "a</span><span style=\"color: green;\">r</span>"
					+ "<span style=\"color: red;\">e</span>"
					+ "<span style=\"color: blue;\">y</span> "
					+ " <span style=\"color: orangered; font-weight: bold;\">Blog</span>"
					+ "</a>. This is your first post. Edit or delete it, "
					+ "then start blogging!";
			article.put(Article.ARTICLE_ABSTRACT, content);
			article.put(Article.ARTICLE_CONTENT, content);
			article.put(Article.ARTICLE_TAGS_REF, "Carey-Blog");
			article.put(Article.ARTICLE_PERMALINK,
					"/carey-blog-hello-wolrd.html");
			article.put(Article.ARTICLE_IS_PUBLISHED, true);
			article.put(Article.ARTICLE_SIGN_REF + "_" + Keys.OBJECT_ID, "0");

			JSONObject requestJSONObject = new JSONObject();
			requestJSONObject.put(Article.ARTICLE, article);
			final String articleId = articleService.addArticle(
					requestJSONObject, request, response).getString(
					Keys.OBJECT_ID);

			requestJSONObject = new JSONObject();
			final String captchaForInit = "captchaForInit";
			request.getSession().setAttribute(CaptchaServlet.CAPTCHA,
					captchaForInit);
			requestJSONObject.put(CaptchaServlet.CAPTCHA, captchaForInit);
			requestJSONObject.put(Keys.OBJECT_ID, articleId);
			requestJSONObject.put(Comment.COMMENT_NAME, "88250");
			requestJSONObject.put(Comment.COMMENT_EMAIL,
					"zhourunsheng2008@gmail.com");
			requestJSONObject
					.put(Comment.COMMENT_URL, "http://www.zhourunsheng.com");
			requestJSONObject
					.put(Comment.COMMENT_CONTENT,
							"Hi, this is a comment. To delete a comment, just log in and "
									+ "view the post's comments. There you will have the option "
									+ "to delete them.");
			AddArticleCommentAction.addArticleComment(requestJSONObject,
					request, response);
		} catch (final Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new RuntimeException("Hello World error?!");
		}
	}

	/**
	 * Initializes administrator.
	 * 
	 * @param request
	 *            the specified http servlet request
	 * @param response
	 *            the specified http servlet response
	 * @throws RepositoryException
	 *             repository exception
	 * @throws JSONException
	 *             json exception
	 */
	private void initAdmin(final HttpServletRequest request,
			final HttpServletResponse response) throws RepositoryException,
			JSONException {
		LOGGER.info("Initializing admin....");
		final Transaction transaction = userRepository.beginTransaction();
		try {
			final JSONObject admin = new JSONObject();

			final com.google.appengine.api.users.User user = userService
					.getCurrentUser();
			final String name = user.getNickname();
			admin.put(User.USER_NAME, name);
			final String email = user.getEmail();
			admin.put(User.USER_EMAIL, email);
			admin.put(User.USER_ROLE, Role.ADMIN_ROLE);

			addUser(admin, request, response);
			transaction.commit();
		} catch (final Exception e) {
			if (transaction.isActive()) {
				transaction.rollback();
			}
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new RuntimeException("Admin init error!");
		}

		LOGGER.info("Initialized admin");
	}

	/**
	 * Initializes statistic.
	 * 
	 * @return statistic
	 * @throws RepositoryException
	 *             repository exception
	 * @throws JSONException
	 *             json exception
	 */
	private JSONObject initStatistic() throws RepositoryException,
			JSONException {
		LOGGER.info("Initializing statistic....");
		final Transaction transaction = userRepository.beginTransaction();
		final JSONObject ret = new JSONObject();
		try {
			ret.put(Keys.OBJECT_ID, Statistic.STATISTIC);
			ret.put(Statistic.STATISTIC_BLOG_ARTICLE_COUNT, 0);
			ret.put(Statistic.STATISTIC_PUBLISHED_ARTICLE_COUNT, 0);
			ret.put(Statistic.STATISTIC_BLOG_VIEW_COUNT, 0);
			ret.put(Statistic.STATISTIC_BLOG_COMMENT_COUNT, 0);
			ret.put(Statistic.STATISTIC_PUBLISHED_BLOG_COMMENT_COUNT, 0);
			statisticRepository.add(ret);
			transaction.commit();
		} catch (final Exception e) {
			if (transaction.isActive()) {
				transaction.rollback();
			}
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new RuntimeException("Statistic init error!");
		}

		LOGGER.info("Initialized statistic");

		return ret;
	}

	/**
	 * Initializes preference.
	 * 
	 * @return preference
	 */
	private JSONObject initPreference() {
		LOGGER.info("Initializing preference....");

		final Transaction transaction = userRepository.beginTransaction();
		final JSONObject ret = new JSONObject();

		try {
			final String preferenceId = PREFERENCE;
			ret.put(NOTICE_BOARD, Preference.Default.DEFAULT_NOTICE_BOARD);
			ret.put(META_DESCRIPTION,
					Preference.Default.DEFAULT_META_DESCRIPTION);
			ret.put(META_KEYWORDS, Preference.Default.DEFAULT_META_KEYWORDS);
			ret.put(HTML_HEAD, Preference.Default.DEFAULT_HTML_HEAD);
			ret.put(ENABLE_POST_TO_BUZZ,
					Preference.Default.DEFAULT_ENABLE_POST_TO_BUZZ);
			ret.put(GOOGLE_OAUTH_CONSUMER_SECRET,
					Preference.Default.DEFAULT_GOOLE_OAUTH_CONSUMER_SECRET);
			ret.put(Preference.RELEVANT_ARTICLES_DISPLAY_CNT,
					Preference.Default.DEFAULT_RELEVANT_ARTICLES_DISPLAY_COUNT);
			ret.put(Preference.RANDOM_ARTICLES_DISPLAY_CNT,
					Preference.Default.DEFAULT_RANDOM_ARTICLES_DISPLAY_COUNT);
			ret.put(Preference.EXTERNAL_RELEVANT_ARTICLES_DISPLAY_CNT,
					Preference.Default.DEFAULT_EXTERNAL_RELEVANT_ARTICLES_DISPLAY_COUNT);
			ret.put(Preference.MOST_VIEW_ARTICLE_DISPLAY_CNT,
					Preference.Default.DEFAULT_MOST_VIEW_ARTICLES_DISPLAY_COUNT);
			ret.put(ARTICLE_LIST_DISPLAY_COUNT,
					Preference.Default.DEFAULT_ARTICLE_LIST_DISPLAY_COUNT);
			ret.put(ARTICLE_LIST_PAGINATION_WINDOW_SIZE,
					Preference.Default.DEFAULT_ARTICLE_LIST_PAGINATION_WINDOW_SIZE);
			ret.put(MOST_USED_TAG_DISPLAY_CNT,
					Preference.Default.DEFAULT_MOST_USED_TAG_DISPLAY_COUNT);
			ret.put(MOST_COMMENT_ARTICLE_DISPLAY_CNT,
					Preference.Default.DEFAULT_MOST_COMMENT_ARTICLE_DISPLAY_COUNT);
			ret.put(RECENT_ARTICLE_DISPLAY_CNT,
					Preference.Default.DEFAULT_RECENT_ARTICLE_DISPLAY_COUNT);
			ret.put(RECENT_COMMENT_DISPLAY_CNT,
					Preference.Default.DEFAULT_RECENT_COMMENT_DISPLAY_COUNT);
			ret.put(BLOG_TITLE, Preference.Default.DEFAULT_BLOG_TITLE);
			ret.put(BLOG_SUBTITLE, Preference.Default.DEFAULT_BLOG_SUBTITLE);
			ret.put(BLOG_HOST, Preference.Default.DEFAULT_BLOG_HOST);
			ret.put(ADMIN_EMAIL, // Current logged in adminstrator's email
					userService.getCurrentUser().getEmail());
			ret.put(LOCALE_STRING, Preference.Default.DEFAULT_LANGUAGE);
			ret.put(ENABLE_ARTICLE_UPDATE_HINT,
					Preference.Default.DEFAULT_ENABLE_ARTICLE_UPDATE_HINT);
			ret.put(Preference.CURRENT_VERSION_NUMBER,
					CareyBlogServletListener.VERSION);
			ret.put(SIGNS, Preference.Default.DEFAULT_SIGNS);
			ret.put(TIME_ZONE_ID, Preference.Default.DEFAULT_TIME_ZONE);

			final String skinDirName = Preference.Default.DEFAULT_SKIN_DIR_NAME;
			ret.put(Skin.SKIN_DIR_NAME, skinDirName);

			final String skinName = skins.getSkinName(skinDirName);
			ret.put(Skin.SKIN_NAME, skinName);

			final Set<String> skinDirNames = skins.getSkinDirNames();
			final JSONArray skinArray = new JSONArray();
			for (final String dirName : skinDirNames) {
				final JSONObject skin = new JSONObject();
				skinArray.put(skin);

				final String name = skins.getSkinName(dirName);
				skin.put(Skin.SKIN_NAME, name);
				skin.put(Skin.SKIN_DIR_NAME, dirName);
			}

			ret.put(Skin.SKINS, skinArray.toString());

			try {
				final String webRootPath = CareyBlogServletListener
						.getWebRoot();
				final String skinPath = webRootPath + Skin.SKINS + "/"
						+ skinDirName;
				Templates.CONFIGURATION
						.setDirectoryForTemplateLoading(new File(skinPath));
			} catch (final IOException e) {
				throw new RuntimeException(e);
			}

			timeZoneUtils.setTimeZone("Asia/Shanghai");

			ret.put(Keys.OBJECT_ID, preferenceId);
			preferenceRepository.add(ret);

			eventManager.fireEventSynchronously(// for upgrade extensions
					new Event<JSONObject>(EventTypes.PREFERENCE_LOAD, ret));

			preferenceRepository.update(preferenceId, ret);
			transaction.commit();
		} catch (final Exception e) {
			if (transaction.isActive()) {
				transaction.rollback();
			}
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new RuntimeException("Preference init error!");
		}

		LOGGER.info("Initialized preference");

		return ret;
	}

	/**
	 * Gets the URL of user logout.
	 * 
	 * @param request
	 *            the specified http servlet request
	 * @param response
	 *            the specified http servlet response
	 * @return logout URL, returns {@code null} if the user is not logged in
	 * @throws ActionException
	 *             action exception
	 * @throws IOException
	 *             io exception
	 */
	public String getLogoutURL(final HttpServletRequest request,
			final HttpServletResponse response) throws ActionException,
			IOException {
		if (!userUtils.isLoggedIn()) {
			response.sendError(HttpServletResponse.SC_FORBIDDEN);
			return null;
		}

		return userService.createLogoutURL("/");
	}

	/**
	 * Gets the URL of user login.
	 * 
	 * @param redirectURL
	 *            redirect URL after logged in
	 * @param request
	 *            the specified http servlet request
	 * @param response
	 *            the specified http servlet response
	 * @return login URL
	 * @throws ActionException
	 *             action exception
	 * @throws IOException
	 *             io exception
	 */
	public String getLoginURL(final String redirectURL,
			final HttpServletRequest request, final HttpServletResponse response)
			throws ActionException, IOException {
		return userService.createLoginURL(redirectURL);
	}

	/**
	 * Gets the {@link AdminService} singleton.
	 * 
	 * @return the singleton
	 */
	public static AdminService getInstance() {
		return SingletonHolder.SINGLETON;
	}

	/**
	 * Private default constructor.
	 */
	private AdminService() {
	}

	/**
	 * Singleton holder.
	 * 
	 */
	private static final class SingletonHolder {

		/**
		 * Singleton.
		 */
		private static final AdminService SINGLETON = new AdminService();

		/**
		 * Private default constructor.
		 */
		private SingletonHolder() {
		}
	}
}
